哈囉大家好!今天我們要來玩點有趣的東西了 —— 我們要開始打造我們的網頁骨架啦!想像一下,我們今天要給我們的 Nuxt3 專案穿上漂亮的衣服,讓它不再只是一個空蕩蕩的框架。準備好了嗎?讓我們開始吧!
首先,讓我們來看看我們要做出來的頁面吧:
聯繫我們頁面:
食物計算機頁面:
是不是看起來很酷?接下來我們會一步一步把這些頁面實現出來 (但不是今天)!
首先,我們要整理一下 app.vue
。這個檔案就像是我們網站的大骨架,所有的頁面都會包在這裡面。
<script setup>
import axios from 'axios'
// 建立一個 Axios 實例,就像是給我們的 API 請求一個專屬的小幫手
const api = axios.create({
baseURL: 'https://two024it-test-app.onrender.com'// 設定基礎 URL
})
const list = ref([])
// 取得資料的函式
async function fetchData() {
try {
const response = await api.get('/freshfoods/')// 取得食物列表
console.log(response.data)// 在控制台印出回應資料
list.value = response.data
} catch (error) {
console.error('哎呀,出錯了:', error)// 錯誤處理
}
}
onMounted(() => {
fetchData()
})
</script>
<template>
<div class="relative flex min-h-screen w-full flex-col">
<!-- 導航欄 -->
<header class="z-20">
<!-- <Header /> -->
</header>
<!-- 頁面內容 -->
<main class="page-wrapper">
<NuxtPage />
</main>
</div>
</template>
<style scoped>
.page-wrapper {
@apply mx-auto flex w-full max-w-[1200px] grow px-3 pb-5 pt-[52px] lg:px-5 lg:pt-[64px];
align-items: start;
}
</style>
這裡我們用了 <NuxtPage />
組件,它就像是一個神奇的佔位符,Nuxt 會自動把我們的頁面內容放在這裡。
接下來,我們要在 pages
資料夾裡新增一個 index.vue
檔案。這就是我們的首頁啦!
<script setup></script>
<template>
<div>index</div>
</template>
<style scoped></style>
當你執行專案並進入首頁時,你會看到 "index" 這個字。這是因為 <NuxtPage />
幫我們把 pages/index.vue
的內容放到那裡了。是不是很神奇?
接著,我們要在 pages
資料夾裡新增所有需要的頁面。我會新增以下頁面:
index.vue
(首頁,已存在)calculator.vue
(食物計算機)food.vue
(營養指南)bird.vue
(百科全書)hospital.vue
(醫護站)connect.vue
(聯繫我們)每個檔案可以使用以下基本結構:
<template>
<div>
<h1>頁面標題</h1>
<!-- 在這裡添加頁面內容 -->
</div>
</template>
<script setup>
// 這裡可以添加需要的 JavaScript 邏輯
</script>
<style scoped>
/* 這裡可以添加頁面專屬的樣式 */
</style>
為了讓我們的網站看起來更漂亮,我們要設定一些基礎的顏色。打開 tailwind.config.ts
,加入以下程式碼:
extend: {
colors: {
blue1: '#e9f1fe',
blue2: '#c4d7ed',
blue3: '#abc8e2',
blue4: '#375d81',
blue5: '#183152',
red1: '#ffaeaa',
red2: '#ed6f69',
bg: '#fff6ea'
}
}
這樣我們就可以用像 text-blue3
這樣的 class 來使用我們自定義的顏色了!超方便的吧?
安裝圖示套件:
開啟終端機,在專案根目錄執行以下指令安裝 Nuxt Icon 模組:
npm install -D @nuxt/icon
設定 Nuxt:
打開 nuxt.config.ts
檔案。
在 modules
陣列中添加 nuxt-icon
:
export default defineNuxtConfig({
devtools: { enabled: true },
modules: ['@nuxtjs/tailwindcss', '@nuxt-icon']
})
使用圖示:
前往 Icones 網站尋找需要的圖示。
複製您選擇的圖示名稱。
在頁面中使用以下格式插入圖示:
<Icon name="ph:hand-tap" size="20"></Icon>
name
屬性中填入您剛剛複製的圖示名稱。
size
屬性用來控制圖示大小。
注意:這邊如果貼在
Header.vue
測試會看不到哦,因為我們app.vue
的Header
元件是註解的狀態 ><
實用提示:
推薦安裝 VS Code 擴充套件:Iconify IntelliSense
這個擴充套件可以將程式碼中的圖示名稱轉換為可視化的圖示,方便預覽和選擇。
完成以上步驟後,您就可以在專案中使用漂亮的圖示了!
components
資料夾在專案根目錄新增一個 components
資料夾。
這個資料夾將用來存放所有的 Vue 元件。
在 components
資料夾內新增一個 Header.vue
檔案。
這個檔案將包含我們的導航欄元件程式碼。
assets
資料夾assets
資料夾。assets
資料夾內新增一個 images
資料夾。assets/images
資料夾中。在這個步驟中,我們將創建一個全局的 CSS 文件,用於定義整個應用程序的基本樣式和一些通用的 CSS 類。
在專案根目錄下創建 assets/css/main.css
文件。
將以下內容複製到 main.css
文件中:
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@100..900&display=swap');
:root {
--color-blue1: #e9f1fe;
--color-blue2: #c4d7ed;
--color-blue3: #abc8e2;
--color-blue4: #375d81;
--color-blue5: #183152;
--color-red1: #ffaeaa;
--color-red2: #ed6f69;
--color-bg: #fff6ea;
}
body {
font-family: 'Noto Sans TC', sans-serif;
letter-spacing: 1.25px;
background: var(--color-bg);
}
/* 消樣式 */
input:focus-visible {
outline: none;
}
textarea:focus-visible {
outline: none;
}
select:focus-visible {
outline: none;
}
/* 圖片通用 */
.pic-auto {
width: 100%;
height: 100%;
object-fit: cover;
vertical-align: middle;
}
/* 限制最大寬度 !> 1200px, 搭配 px-5 使用 */
.page-max-w {
@apply mx-auto w-full max-w-[1200px];
}
/* hover 效果 */
.hover-auto {
@apply transform duration-200;
}
讓我們來解析這個 CSS 文件的各個部分:
:root
選擇器中定義了一系列顏色變數,包括不同深淺的藍色、紅色和背景色。body
元素設置了字體、字間距和背景色。input
、textarea
和 select
元素在獲得焦點時的默認輪廓。.pic-auto
類提供了一種便捷的方式來設置圖片的寬高和適應方式。.page-max-w
類用於限制內容的最大寬度,通常與 padding 一起使用來創建響應式佈局。.hover-auto
類為元素添加了過渡效果,可用於創建平滑的懸停動畫。要在專案中使用這個 CSS 文件,需要在 nuxt.config.ts
中進行配置:
export default defineNuxtConfig({
css: ['~/assets/css/main.css'],
// 其他配置...
})
這樣,main.css
中定義的樣式就會被應用到整個專案中。您可以在元件中直接使用這些 CSS 類和變數,例如:
<template>
<div class="page-max-w">
<img src="..." alt="..." class="pic-auto hover-auto" />
</div>
</template>
<style scoped>
.custom-element {
color: var(--color-blue4);
}
</style>
通過這種方式,我們可以確保整個應用程序有一致的樣式,同時也方便了樣式的管理和修改。
在開始編寫程式碼之前,請仔細查看導航欄的設計稿:
Header.vue
元件的結構和樣式。Header.vue
的內容,實現設計稿中的導航欄功能。首先,我們要在 components
資料夾裡新增一個 Header.vue
檔案。這個檔案會包含我們導航欄的所有程式碼。
<script setup lang="ts">
// 這裡放 JavaScript 程式碼
</script>
<template>
<!-- 這裡放 HTML 結構 -->
</template>
<style scoped>
/* 這裡放 CSS 樣式 */
</style>
這是 Vue 單文件組件的基本結構。<script setup>
區塊用於 JavaScript 邏輯,<template>
用於 HTML 結構,<style scoped>
用於 CSS 樣式。
我們先來放置網站的 Logo:
<template>
<nuxt-link to="/" class="logo fixed left-5 top-2 w-[32px] lg:w-[40px]">
<img src="~/assets/images/logo.svg" alt="logo" class="pic-auto" />
</nuxt-link>
</template>
這段程式碼做了什麼呢?
nuxt-link
是 Nuxt 提供的特殊連結元件,這裡我們用它來連到首頁 "/"
。class
裡的內容是使用 Tailwind CSS 來設定樣式,例如 fixed
表示固定位置,left-5
和 top-2
設定位置,w-[32px]
設定寬度。img
標籤用來顯示 Logo 圖片:
src="~/assets/images/logo.svg"
指定了 Logo 圖片的路徑。~
是 Nuxt.js 中的特殊符號,代表專案的根目錄。這表示圖片位於專案的 assets/images
資料夾中。接下來,我們來做選單按鈕:
<script setup lang="ts">
const route = useRoute()
const menuOpen = ref(false)
const toggleMenu = () => {
menuOpen.value = !menuOpen.value
}
</script>
<template>
<div class="relative">
<button v-show="!menuOpen" @click="toggleMenu" class="nav-btn">
<Icon name="ph:hand-tap" size="20"></Icon>
<div v-if="route.path === '/'">首頁</div>
<div v-else-if="route.path === '/calculator'">食物計算機</div>
<div v-else-if="route.path === '/food'">營養指南</div>
<div v-else-if="route.path === '/bird'">百科全書</div>
<div v-else-if="route.path === '/hospital'">醫護站</div>
<div v-else="route.path === '/connect'">聯繫我們</div>
</button>
</div>
</template>
<style scoped>
.nav-btn {
@apply bg-bg text-blue4 border-blue4 fixed right-0 top-0 z-20 flex w-[140px] items-center justify-center gap-[6px] rounded-bl-[6px] border-b border-l bg-opacity-20 p-[10px] font-bold backdrop-blur-sm;
}
</style>
讓我解釋一下這段程式碼:
const route = useRoute()
讓我們可以知道現在在哪個頁面。menuOpen
是一個變數,用來記錄選單是開啟還是關閉的。toggleMenu
函式用來切換選單的開關狀態。template
中,我們用 v-show="!menuOpen"
來控制按鈕的顯示。@click="toggleMenu"
表示當按鈕被點擊時,會呼叫 toggleMenu
函式。v-if
和 v-else-if
來根據不同的頁面路徑顯示不同的文字。Icon
元件是用來顯示一個小圖示。現在來製作選單的主要內容:
<template>
<div class="relative">
<!-- 前面的按鈕程式碼 -->
<div v-show="menuOpen" class="nav-menu">
<button class="nav-menu-item" @click="toggleMenu">CLOSE</button>
<nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/">首頁</nuxt-link>
<nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/calculator">食物計算機</nuxt-link>
<nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/food">營養指南</nuxt-link>
<nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/bird">百科全書</nuxt-link>
<nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/hospital">醫護站</nuxt-link>
<nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/connect">聯繫我們</nuxt-link>
</div>
</div>
</template>
<style scoped>
.nav-menu {
@apply text-blue4 border-blue4 fixed right-0 top-0 z-20 flex w-[140px] flex-col items-center justify-center rounded-bl-[6px] border-b border-l bg-opacity-20 font-bold backdrop-blur-sm;
}
.nav-menu-item {
@apply flex w-full items-center justify-center p-[10px];
}
</style>
這段程式碼做了什麼:
v-show="menuOpen"
來控制整個選單的顯示。nuxt-link
,點擊後會跳轉到對應的頁面。exact-active-class
用來設定當前頁面的選單項目的樣式。最後,我們來加入一個功能,讓選單在頁面切換時自動關閉:
<script setup lang="ts">
// ... 前面的程式碼
watch(
() => route.path,
(currentPath, previousPath) => {
if (menuOpen.value) {
toggleMenu()
}
}
)
</script>
這段程式碼的作用是:
watch
來監視路由的變化。為了讓我們的導航欄更加吸引人,我們可以加入一些轉場效果。在 Vue 中,我們可以使用 <Transition>
元件來實現這個功能。這個功能不只是讓網頁看起來更酷炫,還能提升使用者體驗,讓介面變化更加流暢自然。
首先,讓我們來看看 Vue 官方文檔中關於 Transition 的說明:Vue Transition 官方文檔
Vue 提供了一個 <Transition>
元件,它可以讓我們輕鬆地為任何元素或元件添加進入/離開的動畫效果。這個元件非常強大,可以處理以下幾種情況:
在我們的導航欄例中,我是這樣使用 Transition 的:
<template>
<div class="relative">
<Transition name="fade">
<!-- 選單按鈕 -->
</Transition>
<Transition name="fade">
<!-- 選單內容 -->
</Transition>
</div>
</template>
這裡的 name="fade"
是很重要的,它定義了我們的動畫效果的名稱。這個名稱會被用來生成 CSS 類別名稱,Vue 會在適當的時機添加或移除這些類別。
當一個元素被 <Transition>
包裹時,Vue 會在元素進入或離開時自動添加/移除一些 CSS 類別。這些類別的名稱是基於我們設定的 name
屬性。
在我們的例子中,因為我們使用了 name="fade"
,所以 Vue 會使用以下的類別:
fade-enter-from
:進入動畫的起始狀態fade-enter-active
:進入動畫的生效狀態fade-enter-to
:進入動畫的結束狀態fade-leave-from
:離開動畫的起始狀態fade-leave-active
:離開動畫的生效狀態fade-leave-to
:離開動畫的結束狀態讓我們來看看我們為導航欄定義的 CSS 樣式:
<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: all 0.4s ease-in-out;
}
.fade-enter-from,
.fade-leave-to {
transform: translatey(-264px);
opacity: 0;
}
</style>
這裡發生了什麼?
.fade-enter-active
和 .fade-leave-active
:
transition: all 0.4s ease-in-out;
表示所有屬性的變化都會在 0.4 秒內完成,使用 ease-in-out 的變化曲線。.fade-enter-from
和 .fade-leave-to
:
transform: translatey(-264px);
表示元素會從上方 264px 的位置移動到原位(或從原位移動到上方 264px 的位置)。opacity: 0;
表示元素會從完全透明變為可見(或從可見變為完全透明)。結合起來,這些 CSS 樣式創造了這樣的效果:
整個過程持續 0.4 秒,使用 ease-in-out 的變化曲線,這會讓動畫看起來更加平滑自然。
components/Header.vue
<script setup lang="ts">
const route = useRoute()
// 選單變數
const menuOpen = ref(false)
// 選單開關
const toggleMenu = () => {
menuOpen.value = !menuOpen.value
}
// 監聽路由的 path 變化, 換頁時自動關閉選單
watch(
() => route.path,
(currentPath, previousPath) => {
if (menuOpen.value) {
toggleMenu()
}
}
)
</script>
<template>
<nuxt-link to="/" class="logo fixed left-5 top-2 w-[32px] lg:w-[40px]">
<img src="~/assets/images/logo.svg" alt="logo" class="pic-auto" />
</nuxt-link>
<div class="relative">
<!-- * 選單按鈕 -->
<Transition name="fade">
<button v-show="!menuOpen" @click="toggleMenu" class="nav-btn">
<Icon name="ph:hand-tap" size="20"></Icon>
<div v-if="route.path === '/'">首頁</div>
<div v-else-if="route.path === '/calculator'">食物計算機</div>
<div v-else-if="route.path === '/food'">營養指南</div>
<div v-else-if="route.path === '/bird'">百科全書</div>
<div v-else-if="route.path === '/hospital'">醫護站</div>
<div v-else="route.path === '/connect'">聯繫我們</div>
</button>
</Transition>
<!-- * 選單 -->
<Transition name="fade">
<div v-show="menuOpen" class="nav-menu">
<button class="nav-menu-item" @click="toggleMenu">CLOSE</button>
<nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/"
>首頁</nuxt-link
>
<nuxt-link
class="nav-menu-item"
exact-active-class="bg-blue4 bg-opacity-15"
to="/calculator"
>食物計算機</nuxt-link
>
<nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/food"
>營養指南</nuxt-link
>
<nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/bird"
>百科全書</nuxt-link
>
<nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/hospital"
>醫護站</nuxt-link
>
<nuxt-link class="nav-menu-item" exact-active-class="bg-blue4 bg-opacity-15" to="/connect"
>聯繫我們</nuxt-link
>
</div>
</Transition>
</div>
</template>
<style scoped>
.nav-btn {
@apply bg-bg text-blue4 border-blue4 fixed right-0 top-0 z-20 flex w-[140px] items-center justify-center gap-[6px] rounded-bl-[6px] border-b border-l bg-opacity-20 p-[10px] font-bold backdrop-blur-sm;
}
.nav-menu {
@apply text-blue4 border-blue4 fixed right-0 top-0 z-20 flex w-[140px] flex-col items-center justify-center rounded-bl-[6px] border-b border-l bg-opacity-20 font-bold backdrop-blur-sm;
}
.nav-menu-item {
@apply flex w-full items-center justify-center p-[10px];
}
.fade-enter-active,
.fade-leave-active {
transition: all 0.4s ease-in-out;
}
.fade-enter-from,
.fade-leave-to {
transform: translatey(-264px);
opacity: 0;
}
</style>
app.vue
<script setup>
import axios from 'axios'
// 創建一個自定義的 Axios 實例
const api = axios.create({
baseURL: 'https://two024it-test-app.onrender.com' // 設置基礎 URL
// timeout: 5000 // 設置超時時間為 5 秒
})
const list = ref([])
// 測試取資料
async function fetchData() {
try {
const response = await api.get('/freshfoods/') // 獲取食物列表
console.log(response.data) // 處理響應數據
list.value = response.data
} catch (error) {
console.error('發生錯誤:', error) // 錯誤處理
}
}
onMounted(() => {
fetchData()
})
</script>
<template>
<div class="app relative flex min-h-screen w-full flex-col">
<!-- * navbar -->
<header class="z-20">
<Header />
</header>
<!-- * pages -->
<main class="page-wrapper">
<NuxtPage />
</main>
</div>
</template>
<style scoped>
.app {
font-family: 'Noto Sans TC', sans-serif;
}
.page-wrapper {
@apply mx-auto flex w-full max-w-[1200px] grow px-3 pb-5 pt-[52px] lg:px-5 lg:pt-[64px];
align-items: start;
}
</style>
nuxt.config.ts
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
modules: ['@nuxtjs/tailwindcss', '@nuxt-icon']
})
tailwind.config.ts
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./components/**/*.{vue,js,ts}', // 掃描所有 Vue 組件
'./layouts/**/*.vue', // 掃描所有布局文件
'./pages/**/*.vue', // 掃描所有頁面文件
'./composables/**/*.{js,ts}', // 掃描所有可組合式函數
'./plugins/**/*.{js,ts}', // 掃描所有插件
'./utils/**/*.{js,ts}', // 掃描所有工具函數
'./{App,app}.{js,ts,vue}', // 掃描主應用文件
'./{Error,error}.{js,ts,vue}', // 掃描錯誤處理文件
'./app.config.{js,ts}' // 掃描應用配置文件
],
theme: {
extend: {
colors: {
blue1: '#e9f1fe',
blue2: '#c4d7ed',
blue3: '#abc8e2',
blue4: '#375d81',
blue5: '#183152',
red1: '#ffaeaa',
red2: '#ed6f69',
bg: '#fff6ea'
}
}
},
plugins: [] // 可以添加 Tailwind 插件
}
assets/css/main.css
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@100..900&display=swap');
:root {
--color-blue1: #e9f1fe;
--color-blue2: #c4d7ed;
--color-blue3: #abc8e2;
--color-blue4: #375d81;
--color-blue5: #183152;
--color-red1: #ffaeaa;
--color-red2: #ed6f69;
--color-bg: #fff6ea;
}
body {
font-family: 'Noto Sans TC', sans-serif;
letter-spacing: 1.25px;
background: var(--color-bg);
}
/* 消樣式 */
input:focus-visible {
outline: none;
}
textarea:focus-visible {
outline: none;
}
select:focus-visible {
outline: none;
}
/* 圖片通用 */
.pic-auto {
width: 100%;
height: 100%;
object-fit: cover;
vertical-align: middle;
}
/* 限制最大寬度 !> 1200px, 搭配 px-5 使用 */
.page-max-w {
@apply mx-auto w-full max-w-[1200px];
}
/* hover 效果 */
.hover-auto {
@apply transform duration-200;
}
好啦!這就是我們超酷導航欄的全部內容了。是不是覺得很有趣?雖然看起來有點複雜,但只要一步一步來,你也可以做出這麼厲害的導航欄喔!,加油!
大家有沒有想要更詳細補充的部分呢?歡迎在下方留言分享喔!讓我們一起在 Nuxt3 的世界中探險吧!加油!
(對了,如果你覺得今天的內容對你有幫助,別忘了給個讚支持一下喔!這會是我繼續努力的動力呢~)